/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * bocciawii
 * Copyright (C) Grupo de trabajo ULE 2010 <ulewii@googlegroups.com>
 * 
 * bocciawii is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * bocciawii is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "elemento-estacionario.h"


ElementoEstacionario::ElementoEstacionario(int no_ocultar)
{
	for (int i = 0; i < 3; i++)
	{
		posicion[i] = 0;
		rotacion[i] = 0;
	}

	for (int i = 0; i < 8; i++)
		for (int j = 0; j < 3; j++)
			bounds[i][j] = 0;

	for (int i = 0; i < 10; i++)
		texturas[i] = 0;
	

	es_transparente = 0;
	es_visible = 1;
	siempre_visible = no_ocultar;

	draw_mode = DRAWING_OBJETO;
	
}

ElementoEstacionario::~ElementoEstacionario()
{
	// Puede que gl se queje cuando tenga que borrar una que se haya borrado
	//  previamente
	glDeleteLists(display_list, 1);
	for (int i = 0; i < 10; i++)
		if (texturas[i] != 0)
		glDeleteTextures(1, &texturas[i]);
}



void ElementoEstacionario::Mover(float x, float y, float z)
{
	posicion[0] = x;
	posicion[1] = y;
	posicion[2] = z;
}

void ElementoEstacionario::Girar(float rx, float ry, float rz)
{
	rotacion[0] = rx;
	rotacion[1] = ry;
	rotacion[2] = rz;
}

void ElementoEstacionario::MoverIncremental(float dx, float dy, float dz)
{
	posicion[0] += dx;
	posicion[1] += dy;
	posicion[2] += dz;
}

void ElementoEstacionario::GirarIncremental(float drx, float dry, float drz)
{
	rotacion[0] += drx;
	rotacion[1] += dry;
	rotacion[2] += drz;
}


void ElementoEstacionario::CargarObjeto(std::string ruta)
{
//	malla = ojbLoader ();
	malla.load((char*)((RUTA_MODELOS + ruta).c_str()));
//	log->log(LOG_MENSAJE, "CargarModelo() Cargado: %s", (RUTA_MODELOS + filename).c_str());

	int indice_tex = 1;
	
	for (int i = 0; i < malla.materialCount; i++)
	{
		if (malla.materialList[i]->texture_id == -1)
		{
			std::string archivo;
			archivo.assign (malla.materialList[i]->texture_filename);
			if (archivo.find('\n') != std::string::npos)
				archivo.erase(archivo.find('\n'));
			CargarTexturas(archivo, indice_tex);
			malla.materialList[i]->texture_id = indice_tex;
			indice_tex++;
		}
	}

	display_list = glGenLists(1);
	id = ruta;
	CrearDisplayList();
	CargarBounds();
}


void ElementoEstacionario::CargarObjeto(ElementoEstacionario* elemento)
{
	for (int i = 0; i < 8; i++)
		for (int j = 0; j < 3; j++)
			bounds[i][j] = elemento->bounds[i][j];

	display_list = elemento->display_list;
	id = elemento->id + "_copia";
}

void ElementoEstacionario::CrearDisplayList()
{
	glNewList(display_list, GL_COMPILE);

		for (int j = 0; j < malla.faceCount; j++)
	{
		int activa_textura = 0;
		GLenum primitiva_gl = GL_TRIANGLES;
		
		obj_material* material = malla.materialList[
								            malla.faceList[j]->material_index
								 ];
		
		
		// Las texturas irán por caras en nuestros modelos.
		int text_id = AplicarMaterial(material);

		if (text_id > 0)
			activa_textura = 1;

		if (malla.faceList[j]->vertex_count == 3)
			primitiva_gl = GL_TRIANGLES;
		else 
			primitiva_gl = GL_QUADS;

		if (activa_textura)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texturas[text_id]); 
		}

		double* vertice;
		double* uv;
		double* normal;

		
		if (EsTransparente())
		{
			glDisable( GL_DEPTH_FUNC );
			glDisable ( GL_LIGHTING );
			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
			glEnable( GL_BLEND ) ;
			glColor4f(material->amb[0], material->amb[1], material->amb[2], transparencia);
		}

		glBegin(primitiva_gl);
		for (int k = 0; k < malla.faceList[j]->vertex_count; k++){
			normal = malla.normalList[
			    malla.faceList[j]->normal_index[k]
			    ]->e;
			vertice = malla.vertexList[
			    malla.faceList[j]->vertex_index[k]
			    ]->e;
			
			if (activa_textura)
			{
				uv = malla.textureList[
								malla.faceList[j]->texture_index[k]
								]->e;
				glTexCoord2f(uv[0], uv[1]);
			}
			
			glNormal3f(normal[0], normal[1], normal[2]);
			glVertex3f(vertice[0], vertice[1], vertice[2]);
		} // Por vértices
		glEnd();

	if (activa_textura)
		glDisable(GL_TEXTURE_2D);

	}// Por cara

	if (EsTransparente())
		{
			glEnable( GL_DEPTH_FUNC );
			glEnable ( GL_LIGHTING );
			glDisable( GL_BLEND ) ;
		}

	glEndList();

//	std::cerr << display_list;
	
}

void ElementoEstacionario::CargarBounds()
{
	float min_x = 100, min_y = 100, min_z = 100;
	float max_x = -100, max_y = -100, max_z = -100;

	for (int i = 0; i < malla.vertexCount; i++)
	{
		double vertice[3];
		vertice[0] = malla.vertexList[i]->e[0];
		vertice[1] = malla.vertexList[i]->e[1];
		vertice[2] = malla.vertexList[i]->e[2];
		
		if (vertice[0] < min_x)
			min_x = vertice[0];

		if (vertice[0] > max_x)
			max_x = vertice[0];

		if (vertice[1] < min_y)
			min_y = vertice[1];

		if (vertice[1] > max_y)
			max_y = vertice[1];

		if (vertice[2] < min_z)
			min_z = vertice[2];

		if (vertice[2] > max_z)
			max_z = vertice[2];	
	}

	min_x = 0.01 * round (min_x * 100.0);
	min_y = 0.01 * round (min_y * 100.0);
	min_z = 0.01 * round (min_z * 100.0);

	max_x = 0.01 * round (max_x * 100.0);
	max_y = 0.01 * round (max_y * 100.0);
	max_z = 0.01 * round (max_z * 100.0);

	bounds[0] = {min_x, min_y, min_z};
	bounds[1] = {max_x, min_y, min_z};
	bounds[2] = {max_x, min_y, max_z};
	bounds[3] = {min_x, min_y, max_z};

	bounds[4] = {min_x, max_y, min_z};
	bounds[5] = {max_x, max_y, min_z};
	bounds[6] = {max_x, max_y, max_z};
	bounds[7] = {min_x, max_y, max_z};

	
//	std::cerr << "(" << min_x << ", " << min_y << ", " << min_z << ") ";
//	std::cerr << "(" << max_x << ", " << max_y << ", " << max_z << ") " << std::endl;


}



void ElementoEstacionario::CargarTexturas(std::string ruta, int indice_tex)
{
	SDL_Surface *imagen;
	GLenum formato_textura;

	imagen = LoadPNG(RUTA_MODELOS + ruta);
    if (!imagen) 
	{
		//TODO gestión errore;
	}

	
	if (imagen->format->BytesPerPixel == 4)     // contains an alpha channel
	{
		if (imagen->format->Rmask == 0x000000ff)
			formato_textura = GL_RGBA;
		else
			formato_textura = GL_BGRA;
	} else if (imagen->format->BytesPerPixel == 3)     // no alpha channel
	{
		if (imagen->format->Rmask == 0x000000ff)
			formato_textura = GL_RGB;
		else
			formato_textura = GL_BGR;
	} else {
		printf("warning: the image is not truecolor..  this will probably break\n");
		// TODO Gestión errores
	}
	
    // Create Textures	   
	glGenTextures(1, &texturas[indice_tex]);
	glBindTexture(GL_TEXTURE_2D, texturas[indice_tex]);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//TODO modos textura, decal, blending, etcétera, hay que modificar el loader de objs.
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	gluBuild2DMipmaps(GL_TEXTURE_2D, 4, imagen->w, imagen->h,formato_textura, GL_UNSIGNED_BYTE, imagen->pixels);

	SDL_FreeSurface(imagen);
}


SDL_Surface* ElementoEstacionario::LoadPNG (std::string ruta){
    Uint8 *rowhi, *rowlo;
    Uint8 *tmpbuf, tmpch;
    SDL_Surface *image;
    int i, j;

	SDL_RWops *rwop;
	rwop = SDL_RWFromFile(ruta.c_str(), "rb");
	image = IMG_LoadPNG_RW(rwop);

    if ( image == NULL ) {
		//TODO gestión de erroes
	}

	// Invertimos la imagen.
	InvertSurface(image);
	
    return(image);
}


// Gracias a las listas de SDL!
// Eric Wing - Thu Sep 12 17:48:01 PDT 2002
int ElementoEstacionario::InvertImage(int pitch, int height, void* image_pixels)
{
	int index;
	void* temp_row;
	int height_div_2;

	temp_row = (void *)malloc(pitch);
	if(NULL == temp_row)
	{
		SDL_SetError("Not enough memory for image inversion");
		return -1;
	}
	/* if height is odd, don't need to swap middle row */
	height_div_2 = (int) (height * .5);
	for(index = 0; index < height_div_2; index++) 	{
		/* uses string.h */
		memcpy((Uint8 *)temp_row,
			(Uint8 *)(image_pixels) +
			pitch * index,
			pitch);

		memcpy(
			(Uint8 *)(image_pixels) + 
			pitch * index,
			(Uint8 *)(image_pixels) + 
			pitch * (height - index-1),
			pitch);
		memcpy(
			(Uint8 *)(image_pixels) + 
			pitch * (height - index-1),
			temp_row,
			pitch);
	}
	free(temp_row);
	return 0;
}


// Eric Wing - Thu Sep 12 17:48:01 PDT 2002
int ElementoEstacionario::InvertSurface(SDL_Surface* image)
{
	if(NULL == image)
	{
		SDL_SetError("Surface is NULL");
		return -1;
	}
	return( InvertImage(image->pitch, image->h, 
			image->pixels) );
}


void ElementoEstacionario::Dibujate()
{

	if (!EsVisible() && !siempre_visible)
		return; 

	if (draw_mode == DRAWING_BOXES)
	{
		DibujarCaja();
		return;
	}
	
	glPushMatrix();
	glTranslatef(posicion[0], posicion[1], posicion[2]);
	glRotatef(rotacion[0], 1,0,0);
	glRotatef(rotacion[1], 0,1,0);
	glRotatef(rotacion[2], 0,0,1);

	glCallList(display_list);
/*
	for (int j = 0; j < malla.faceCount; j++)
	{
		int activa_textura = 0;
		GLenum primitiva_gl = GL_TRIANGLES;
		
		obj_material* material = malla.materialList[
								            malla.faceList[j]->material_index
								 ];
		
		
		// Las texturas irán por caras en nuestros modelos.
		int text_id = AplicarMaterial(material);

		if (text_id > 0)
			activa_textura = 1;

		if (malla.faceList[j]->vertex_count == 3)
			primitiva_gl = GL_TRIANGLES;
		else 
			primitiva_gl = GL_QUADS;

		if (activa_textura)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texturas[text_id]); 
		}

		double* vertice;
		double* uv;
		double* normal;

		
		if (EsTransparente())
		{
			glDisable( GL_DEPTH_FUNC );
			glDisable ( GL_LIGHTING );
			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
			glEnable( GL_BLEND ) ;
			glColor4f(material->amb[0], material->amb[1], material->amb[2], transparencia);
		}

		glBegin(primitiva_gl);
		for (int k = 0; k < malla.faceList[j]->vertex_count; k++){
			normal = malla.normalList[
			    malla.faceList[j]->normal_index[k]
			    ]->e;
			vertice = malla.vertexList[
			    malla.faceList[j]->vertex_index[k]
			    ]->e;
			
			if (activa_textura)
			{
				uv = malla.textureList[
								malla.faceList[j]->texture_index[k]
								]->e;
				glTexCoord2f(uv[0], uv[1]);
			}
			
			glNormal3f(normal[0], normal[1], normal[2]);
			glVertex3f(vertice[0], vertice[1], vertice[2]);
		} // Por vértices
		glEnd();


	}// Por cara

*/

//	if (glIsEnabled(GL_TEXTURE_2D))
//		glDisable(GL_TEXTURE_2D);
	
//	if (glIsEnabled (GL_BLEND))
//	{
//		glDisable (GL_BLEND);
//		glEnable (GL_DEPTH_FUNC);
//		glEnable (GL_LIGHTING);
//	}
	glPopMatrix();
}


void ElementoEstacionario::DibujarCaja()
{

	glPushMatrix();
	glTranslatef(posicion[0], posicion[1], posicion[2]);
	glRotatef(rotacion[0], 1,0,0);
	glRotatef(rotacion[1], 0,1,0);
	glRotatef(rotacion[2], 0,0,1);

	
	glColor3f(1.0f, 1.0f, 1.0f);
	glDisable(GL_LIGHTING);

	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	
	glBegin(GL_QUADS);
		for (int i = 0; i < 8; i++)
			glVertex3f(bounds[i][0], bounds[i][1], bounds[i][2]);
	glEnd();

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	

		for (int i = 0; i < 4; i++)
		{
			glBegin(GL_LINE_STRIP);
			glVertex3f(bounds[i][0], bounds[i][1], bounds[i][2]);
			glVertex3f(bounds[i+4][0], bounds[i+4][1], bounds[i+4][2]);
			glEnd();
		}
	
	glEnable(GL_LIGHTING);
	glPopMatrix();
}



int ElementoEstacionario::AplicarMaterial(obj_material* material)
{
	GLfloat mat_ambiente[] = 
	{
		material->amb[0],
		material->amb[1],
		material->amb[2],
	};
	GLfloat mat_difuso[] =		
	{
		material->diff[0],
		material->diff[1],
		material->diff[2],
	};
	GLfloat mat_especular[] =
	{
		material->spec[0],
		material->spec[1],
		material->spec[2],
	};
	//			GLfloat mat_emision[];
	GLfloat mat_brillo []=
	{
		material->shiny
	};

	glMaterialfv(GL_FRONT, 
	    GL_AMBIENT,
	    mat_ambiente);

	glMaterialfv(GL_FRONT, 
	    GL_DIFFUSE,
	    mat_difuso);

	glMaterialfv(GL_FRONT, 
	    GL_SPECULAR,
	    mat_especular);

	glMaterialfv(GL_FRONT, 
	    GL_SHININESS,
	    mat_brillo);

	if (material->trans != 1)
		es_transparente = 1;
	else es_transparente = 0;
	
	transparencia = material->trans;

	return material->texture_id;
}

int ElementoEstacionario::EnPuntoDeVista(float x, float y)
{
	if (siempre_visible) 
		return 1;
	
	es_visible = 0;

	
	double res_x, res_y, res_z;

	GLdouble model_view[16];
	glGetDoublev(GL_MODELVIEW_MATRIX, model_view);

	GLdouble projection[16];
	glGetDoublev(GL_PROJECTION_MATRIX, projection);

	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);


	for (int i = 0; i < 8; i++)
	{
		float punto[3];
		punto[0] = bounds[i][0] + posicion[0];
		punto[1] = bounds[i][1] + posicion[1];
		punto[2] = bounds[i][2] + posicion[2];

		gluProject(
		    punto[0], punto[1], punto[2],
		    model_view, projection, viewport, 
		    &res_x, &res_y, &res_z);
		
		if ((res_x > 0 && res_x < x) && 
			(res_y > 0 && res_y < y) && res_z < 1)
		{
			es_visible = 1;
			return es_visible;
		}



		
	}

	return es_visible;
	
	

}
